The symmetry-exceptional slopes of K=o9_30634 are (-2,1), (-1,1), and (2,1). All three have symmetry group Z/2+Z/2 and thus they can be written in three different ways as DBC. Here we determine all these three ways. Since K is strongly invertible all slopes can be written in one way as DBC branched along a link L in S3. That means for the three symmetry-exceptional fillings we need to find another 2 ways to write them as DBCs.
import time
import snappy
import pandas
DBChomologies = pandas.read_csv("DBChomologies_non_alternating.csv") # We load a list of all non alternating links in
#the HTLinkExteriors together with their homologies (if the homology is cyclic) and their crossing numbers.
### REMARK: To build the list we need to run the code in the file Build_DBChomologies which takes around 60 minutes.
#Here we just load it.
def double_branched_cover(link):
"""
Returns the double branched cover of the link.
"""
L=link.copy()
for i in range(L.num_cusps()):
L.dehn_fill((2,0),i)
for cov in L.covers(2):
if (2.0, 0.0) not in cov.cusp_info('filling'):
return cov
def better_is_isometric_to(X,Y,index):
"""
Returns True if X and Y are isometric.
Returns False if X and Y have different homologies. TO DO: Use volume to rigorously distinguish X and Y.
Returns 'unclear' if SnapPy cannot verify it.
The higher the index the harder SnapPy tries.
"""
w='unclear'
if X.homology()!=Y.homology():
w=False
if w=='unclear':
for i in (0,index):
try:
w=X.is_isometric_to(Y)
except RuntimeError:
pass
except snappy.SnapPeaFatalError:
pass
if w==True:
break
if w==False:
w='unclear'
X.randomize()
Y.randomize()
i=i+1
return w
def possible_DBC(homologies,max_crossings=15):
"""
Takes a list of orders of homologies and returns a list consisting of all DBC of alternating links in the HT link table with that homologies together with the link names.
"""
DBCList=[]
LINKS=[]
for order in homologies:
LINKS=LINKS+DBChomologies.loc[(DBChomologies['homology']==order) & (DBChomologies['crossings']<=max_crossings)]['knot'].tolist()
for link in LINKS:
L=snappy.Manifold(link)
D=double_branched_cover(L)
DBCList.append([D,link])
return DBCList
### The following two functions are written by Dunfield and search for positive triangulations.
def all_positive(manifold):
return manifold.solution_type() == 'all tetrahedra positively oriented'
def find_positive_triangulation(manifold, tries=100):
M = manifold.copy()
for i in range(tries):
if all_positive(M):
return M
M.randomize()
for d in M.dual_curves():
X = M.drill(d)
X = X.filled_triangulation()
X.dehn_fill((1,0))
for i in range(tries):
if all_positive(X):
return X
X.randomize()
# In the closed case, here is another trick.
if all(not c for c in M.cusp_info('is_complete')):
for i in range(tries):
# Drills out a random edge
X = M.__class__(M.filled_triangulation())
if all_positive(X):
return X
M.randomize()
def better_find_positive_triangulation(M,tries=1):
'''
Search for a positive triangulation, but ignores errors.
'''
RandomizeCount=0
while RandomizeCount<tries:
try:
X=find_positive_triangulation(M)
return X
except snappy.SnapPeaFatalError:
M.randomize()
RandomizeCount=RandomizeCount+1
return None
def is_alternating(knot,slope,try_hard=False,index=10,tries=1,max_cro=15):
'''
Checks if the slope is alternating.
'''
K=snappy.Manifold(knot)
K.dehn_fill(slope)
DBC=possible_DBC([K.homology().order()],max_crossings=max_cro)
for D in DBC:
w=better_is_isometric_to(D[0],K,index)
if w==True:
return [slope,D[1]]
if try_hard:
X=better_find_positive_triangulation(K,tries)
if X is not None:
for D in DBC:
Y=better_find_positive_triangulation(D[0],tries)
if Y is not None:
w=better_is_isometric_to(X,Y,index)
if w==True:
return [slope,D[1]]
return False
K=snappy.Manifold('o9_30634')
exc_sym_slopes=[(-2,1),(-1,1),(2,1)]
for s in exc_sym_slopes:
K.dehn_fill(s)
print(s,K.symmetry_group())
(-2, 1) Z/2 + Z/2 (-1, 1) Z/2 + Z/2 (2, 1) Z/2 + Z/2
start_time = time.time()
branching_sets=[]
knot='o9_30634'
for slope in exc_sym_slopes:
w=is_alternating(knot,slope,index=25,max_cro=15)
if w!=False:
branching_sets.append([knot,w[0],w[1]])
print('We found a branching set:',knot,slope,w[1])
print('Total number of branching sets we have found:', len(branching_sets))
print('Total time taken: %s minutes ---' % ((time.time() - start_time)/60))
We found a branching set: o9_30634 (-1, 1) K13n2148 Total number of branching sets we have found: 1 Total time taken: 0.8800097187360127 minutes ---
Next we will search for branching sets in more general manifolds. For that we will take links in the HT link tables fill some one component of it to get a surgery diagram of a link in a manifold. Then we take the double branched cover of that link and search for a match with a symmetric filling on A.
DBChomologies_branching = pandas.read_csv("DBChomologies_one_filling.csv")
### REMARK: To build the list we need to run the code in the file Build_DBChomologies which takes around 3 days. Here we just load it.
def double_branched_cover(link):
"""
Returns the double branched covers of the link. This works also for links in a more general manifold.
Note that a knot in a more general manifold may have more than one double branched cover
(or no double branched cover at all if the knot represents a primitive element in homology).
This function will return the complete list of all double branched covers of the link.
"""
L=link.copy()
for i in range(L.num_cusps()):
if L.cusp_info(i).filling==(0.0, 0.0):
L.dehn_fill((2,0),i)
return [cov for cov in L.covers(2) if (2.0, 0.0) not in cov.cusp_info('filling')]
def possible_DBC_surgery_diagrams(homologies,max_crossings=15):
"""
Reads off the possible surgery diagrams.
"""
DBCList=[]
LINKS=[]
CUSPS=[]
SLOPES_STRINGS=[]
for order in homologies:
LINKS=LINKS+DBChomologies_branching.loc[(DBChomologies_branching['homology']==order) & (DBChomologies_branching['crossings']<=max_crossings)]['knot'].tolist()
CUSPS=CUSPS+DBChomologies_branching.loc[(DBChomologies_branching['homology']==order) & (DBChomologies_branching['crossings']<=max_crossings)]['cusp'].tolist()
SLOPES_STRINGS=SLOPES_STRINGS+DBChomologies_branching.loc[(DBChomologies_branching['homology']==order) & (DBChomologies_branching['crossings']<=max_crossings)]['filling'].tolist()
SLOPES=[]
for string in SLOPES_STRINGS:
string_without_brackets=string[1:-1]
SLOPES.append(tuple(map(int, string_without_brackets.split(', '))))
for i in range(0,len(LINKS)):
DBCList.append([LINKS[i],SLOPES[i],CUSPS[i]])
return DBCList
def search_for_branching_set(knot,slope,try_hard=False,index=10,tries=1,max_crossings=15):
'''
Searchs for a surgery diagram of the rbanching set
'''
K=snappy.Manifold(knot)
K.dehn_fill(slope)
DBC=possible_DBC_surgery_diagrams([K.homology().order()],max_crossings)
for DIAGRAM in DBC:
L=snappy.Manifold(DIAGRAM[0])
L.dehn_fill(DIAGRAM[1],DIAGRAM[2])
for D in double_branched_cover(L):
w=better_is_isometric_to(D,K,index)
if w==True:
return [[knot,slope,DIAGRAM[0],DIAGRAM[1],DIAGRAM[2]]]
if try_hard:
X=better_find_positive_triangulation(K,tries)
if X is not None:
for DIAGRAM in DBC:
L=snappy.Manifold(DIAGRAM[0])
L.dehn_fill(DIAGRAM[1],DIAGRAM[2])
for D in double_branched_cover(L):
Y=better_find_positive_triangulation(D,tries)
if Y is not None:
w=better_is_isometric_to(X,Y,index)
if w==True:
return [[knot,slope,DIAGRAM[0],DIAGRAM[1],DIAGRAM[2]]]
return False
def search_for_three_branching_sets(knot,slope,try_hard=False,index=10,tries=1,max_crossings=15):
'''
Searchs for a surgery diagram of the branching set
'''
BRANCHING_SETS=[]
K=snappy.Manifold(knot)
K.dehn_fill(slope)
homologies=[]
DBC=possible_DBC_surgery_diagrams([K.homology().order()],max_crossings)
for DIAGRAM in DBC:
L=snappy.Manifold(DIAGRAM[0])
L.dehn_fill(DIAGRAM[1],DIAGRAM[2])
for D in double_branched_cover(L):
w=better_is_isometric_to(D,K,index)
if w==True:
BRANCHING_SETS=BRANCHING_SETS+[[knot,slope,DIAGRAM[0],DIAGRAM[1],DIAGRAM[2]]]
for i in range(L.num_cusps()):
if L.cusp_info(i).filling==(0.0, 0.0):
L.dehn_fill((1,0),i)
if L.homology().order() not in homologies:
homologies.append(L.homology().order())
if len(homologies)==3:
return BRANCHING_SETS
if try_hard:
X=better_find_positive_triangulation(K,tries)
if X is not None:
for DIAGRAM in DBC:
L=snappy.Manifold(DIAGRAM[0])
L.dehn_fill(DIAGRAM[1],DIAGRAM[2])
for D in double_branched_cover(L):
Y=better_find_positive_triangulation(D,tries)
if Y is not None:
w=better_is_isometric_to(X,Y,index)
if w==True:
BRANCHING_SETS=BRANCHING_SETS+[[knot,slope,DIAGRAM[0],DIAGRAM[1],DIAGRAM[2]]]
for i in range(L.num_cusps()):
if L.cusp_info(i).filling==(0.0, 0.0):
L.dehn_fill((1,0),i)
if L.homology().order() not in homologies:
homologies.append(L.homology().order())
if len(homologies)==3:
return BRANCHING_SETS
return BRANCHING_SETS
start_time = time.time()
knot='o9_30634'
for slope in exc_sym_slopes:
w=search_for_three_branching_sets(knot,slope,index=3,max_crossings=15)
if w==[]:
w=False
if w!=False:
branching_sets=branching_sets+w
print('We found a branching set:',w)
print('Total number of branching sets we have found:', len(branching_sets))
print('Total time taken: %s minutes ---' % ((time.time() - start_time)/60))
We found a branching set: [['o9_30634', (-2, 1), 'L12n1012', (-8, 3), 0], ['o9_30634', (-2, 1), 'L14n23888', (-2, 1), 0], ['o9_30634', (-2, 1), 'L14n24646', (-2, 1), 0]] We found a branching set: [['o9_30634', (-1, 1), 'L10n45', (-5, 1), 0], ['o9_30634', (-1, 1), 'L10n49', (3, 4), 0], ['o9_30634', (-1, 1), 'L11n141', (-3, 1), 0], ['o9_30634', (-1, 1), 'L14a20418', (3, 1), 0], ['o9_30634', (-1, 1), 'L14a20418', (3, 1), 1]] We found a branching set: [['o9_30634', (2, 1), 'L13n9873', (-2, 1), 1]] Total number of branching sets we have found: 10 Total time taken: 164.701756131649 minutes ---
branching_sets
[['o9_30634', (-1, 1), 'K13n2148'], ['o9_30634', (-2, 1), 'L12n1012', (-8, 3), 0], ['o9_30634', (-2, 1), 'L14n23888', (-2, 1), 0], ['o9_30634', (-2, 1), 'L14n24646', (-2, 1), 0], ['o9_30634', (-1, 1), 'L10n45', (-5, 1), 0], ['o9_30634', (-1, 1), 'L10n49', (3, 4), 0], ['o9_30634', (-1, 1), 'L11n141', (-3, 1), 0], ['o9_30634', (-1, 1), 'L14a20418', (3, 1), 0], ['o9_30634', (-1, 1), 'L14a20418', (3, 1), 1], ['o9_30634', (2, 1), 'L13n9873', (-2, 1), 1]]
Since the knot K is strongly invertible we have always at least on way to write the knot as a DBC over a link in S3. by checking that the above surgery links always have the filling knot an unknot we see that we can write the (-1,1)-and the (-2,1)-filling in two more ways as DBC over a link in a lens space. (These two ways are distinguished by the homology of the lens space. This shows that these two slopes can only be written in a single way as the DBC over a link in S3. For the (2,1)-filling we found a surgery description along a link in RP3. Here we need to find another way to write it as the DBC. For that we search for surgery descriptions along symmetric links.
def better_is_isometric_to(X,Y,index=10,return_isometries=False):
"""
Returns True if X and Y are isometric.
Returns False if it is unclear.
"""
w=False
for i in (0,index):
try:
w=X.is_isometric_to(Y,return_isometries)
return w
except (RuntimeError, snappy.SnapPeaFatalError):
X.randomize()
Y.randomize()
i=i+1
return w
def getting_surgery_diagrams(M,max_diagrams=1,max_drill_range=100,max_drill_depth=15,symmetric=False,invertible=False,DIAGRAMS=[]):
'''
Returns surgery diagrams of the input manifold.
max_diagrams is the number of diagrams that is searched for.
If the flag symmetric is set, only symmetric surgery diagrams gets returned.
If the flag invertible is set, only surgery diagrams along strongly invertible links gets returned.
max_drill_range gives the range of geodesics that are drilled (this should be large if searched for invertible).
max_drill_depth gives the number of components of the surgery diagrams
(this should be large if searched for symmetric).
'''
for i in range(min(len(M.dual_curves()),max_drill_range)):
Mi=M.drill(i)
if Mi in snappy.HTLinkExteriors():
MID=Mi.identify()[-1]
if MID.name()[0]=='o':
MID=Mi.identify()[-2]
DIAG=getting_surgery_coefficients(Mi,MID)
if invertible==False and symmetric==False:
DIAGRAMS=DIAGRAMS+DIAG
if invertible:
for D in DIAG:
link=D.copy()
link.dehn_fill([(0,0)] * link.num_cusps())
S=better_symmetry_group(link)[0]
if S!='unclear':
if is_strongly_invertible_link(S):
DIAGRAMS=DIAGRAMS+[[D,'invertible']]
if symmetric:
for D in DIAG:
if is_symmetric(D)==True:
DIAGRAMS=DIAGRAMS+[[D,'symmetric']]
if len(DIAGRAMS)>=max_diagrams:
return DIAGRAMS
if len(Mi.name())>max_drill_depth:
return DIAGRAMS+[]
for i in range(min(len(M.dual_curves()),max_drill_range)):
Mi=M.drill(i)
DIAGRAMS=getting_surgery_diagrams(Mi,max_diagrams,max_drill_range,max_drill_depth,symmetric,invertible,DIAGRAMS)
if len(DIAGRAMS)>=max_diagrams:
return DIAGRAMS
return DIAGRAMS+[]
def getting_surgery_coefficients(Mi,MID):
'''
Gives the surgery diagrams.
'''
surgerydescriptions=[]
isoms=better_is_isometric_to(Mi,MID,return_isometries=True)
if isoms==False:
return []
for isom in isoms:
maps = isom.cusp_maps()
cusps = isom.cusp_images()
meridimages = [mat.column(0) for mat in maps] # pulls off first columns of matrices of action, we want inital one to be (1,0) or (-1,0)
dfslopes = [y for x,y in sorted(zip(cusps,meridimages))] #reorders slopes according to map of cusps
surgdesc=snappy.Manifold(MID.name())
surgdesc.dehn_fill(dfslopes)
surgerydescriptions.append(surgdesc)
return surgerydescriptions
def better_symmetry_group(M,index=10):
'''
Computes the symmetry group.
'''
randomizeCount=0
while randomizeCount<index:
try:
S=M.symmetry_group()
full=S.is_full_group()
return (S,full)
except (ValueError, RuntimeError, snappy.SnapPeaFatalError):
M.randomize()
randomizeCount=randomizeCount+1
return ('unclear',False)
def is_strongly_invertible_link(symmetry_group_of_link):
'''
Checks if a link is strongly invertible.
'''
S=symmetry_group_of_link
ISOM=S.isometries()
for I in ISOM:
if I.extends_to_link():
if I.cusp_images()==list(range(I.num_cusps())):
if I.cusp_maps()==[matrix([[-1,0],[0,-1]])] * I.num_cusps():
return True
return False
def is_symmetric(surgery_diagram):
'''
Checks if the given surgery diagram is symmetric.
'''
L=surgery_diagram.copy()
D=surgery_diagram.copy()
D.dehn_fill([(0,0)] * D.num_cusps())
S=better_symmetry_group(D)[0]
if S=='unclear':
return False
ISOM=S.isometries()
for I in ISOM:
if I.extends_to_link():
cusp_check=[]
for i in range(I.num_cusps()):
if (I.cusp_images()[i]==i and I.cusp_maps()[i]!=matrix([[-1,0],[0,-1]])):
break
if I.cusp_images()[i]!=i:
if I.cusp_maps()[i]!=I.cusp_maps()[I.cusp_images()[i]]:
break
if I.cusp_maps()[i] not in [matrix([[1,0],[0,1]]), matrix([[-1,0],[0,-1]])]:
break
if I.cusp_maps()[I.cusp_images()[i]] not in [matrix([[1,0],[0,1]]), matrix([[-1,0],[0,-1]])]:
break
if L.cusp_info()[I.cusp_images()[i]].filling not in [L.cusp_info()[i].filling,(-L.cusp_info()[i].filling[0],-L.cusp_info()[i].filling[1])]:
break
cusp_check.append(i)
if len(cusp_check)==I.num_cusps():
return True
return False
K
o9_30634(2,1)
diag=getting_surgery_diagrams(K,max_diagrams=10,symmetric=True)
diag
[[L13n9734(0,-1)(4,1)(-2,1), 'symmetric'], [L13n9734(0,1)(-4,-1)(2,-1), 'symmetric'], [L13n10114(0,-1)(0,-1)(0,-1), 'symmetric'], [L13n10114(0,1)(0,1)(0,1), 'symmetric'], [L12n2176(1,4)(4,-1)(-5,-2)(3,2), 'symmetric'], [L12n2176(1,4)(4,-1)(-3,-2)(5,2), 'symmetric'], [L12n2176(-1,-4)(-4,1)(3,2)(-5,-2), 'symmetric'], [L12n2176(-1,-4)(-4,1)(5,2)(-3,-2), 'symmetric'], [L14n62837(-4,1)(0,-1)(-1,2)(0,-1), 'symmetric'], [L14n62837(5,1)(0,1)(-1,-2)(-4,-1), 'symmetric'], [L14n62837(-1,-2)(-9,-5)(2,1)(5,2), 'symmetric'], [L14n62837(1,2)(9,4)(-2,1)(-3,-2), 'symmetric'], [L14n62837(-1,-2)(-9,-4)(2,-1)(3,2), 'symmetric'], [L14n62837(1,2)(9,5)(-2,-1)(-5,-2), 'symmetric'], [L14n62837(-5,-1)(0,-1)(1,2)(4,1), 'symmetric'], [L14n62837(4,-1)(0,1)(1,-2)(0,1), 'symmetric']]
New triangulation received from PLink!
All surgery diagrams are strongly invertible. We build the quotients by hand. For L13n9734 it is not hard to find the branching set. It has the following PD code:
B=snappy.Link([(8, 37, 9, 10), (2, 16, 3, 15), (7, 16, 8, 17), (17, 6, 18, 7), (3, 20, 4, 21), (23, 12, 24, 13), (11, 24, 12, 25), (25, 10, 26, 11), (9, 27, 0, 26), (27, 1, 28, 0), (13, 28, 14, 29), (22, 30, 23, 29), (30, 5, 31, 6), (31, 19, 32, 18), (4, 33, 5, 34), (19, 33, 20, 32), (36, 1, 37, 2), (14, 36, 15, 35), (21, 34, 22, 35)]
)
B
<Link: 2 comp; 19 cross>
First we check that it is the correct DBC:
double_branched_cover(B.exterior()).is_isometric_to(K)
True
B.exterior().volume(verified=True)
12.1514600187?
B.jones_polynomial()
q^-19 - 2*q^-17 + 3*q^-15 - 3*q^-13 + 3*q^-11 - 2*q^-9 + q^-7 - q^-3 + 2*q^-1
B.simplify('global')
B
<Link: 2 comp; 16 cross>
print(B.braid_word())
[-1, -2, 3, 4, 3, -5, 4, 3, 2, 4, 1, 3, -5, 2, 4, 5, 6, 5, -7, -6, 5, -4, 3, -5, -4, -5, 6, -5, 7, 4, -3, -2, -3, 4, -5, -6]
For the second branching set we take the surgery description from [Baker--Kegel 2022] and build the quotient by hand. We get a branching set with the following PD code:
B=snappy.Link([(5, 0, 6, 1), (11, 6, 0, 7), (10, 30, 11, 29), (28, 10, 29, 9), (21, 2, 22, 3), (22, 31, 23, 32), (1, 20, 2, 21), (30, 19, 31, 20), (23, 19, 24, 18), (17, 25, 18, 24), (25, 17, 26, 16), (15, 27, 16, 26), (27, 15, 28, 14), (13, 8, 14, 9), (7, 12, 8, 13), (33, 4, 12, 5), (3, 32, 4, 33)])
B
<Link: 2 comp; 17 cross>
First we check that it is the correct DBC:
double_branched_cover(B.exterior()).is_isometric_to(K)
True
B.exterior().volume(verified=True)
12.1514600187?
So it appears to have the same volume. However the Jones polynomial is different:
B.jones_polynomial()
2*q^-11 - q^-9 + q^-5 - 2*q^-3 + 3*q^-1 - 3*q + 3*q^3 - 2*q^5 + q^7
B.simplify('global')
B
<Link: 2 comp; 16 cross>
print(B.braid_word())
[-1, 2, 3, -4, 5, -6, 7, 8, -9, -8, -7, 6, 5, 4, -3, -2, 1, 3, -4, 5, -6, 5, 7, 4, 8, -3, -5, 9, 2, 6, -5, 7, -4, -6, 3, -5, -6, -5, -4, -5, -6, -7, -6, -8, -5, -7, 4, -3, 5, -2, 6, 5, 4, -5]
It remains to check that they both are not KH thin. For that we build the alphabetical braid words and use Knot Job to compute the KH.
import string
def braid_word_to_letters(word):
'''Returns an alphabetical describtion of the braid word.'''
upper=list(string.ascii_uppercase)
lower=list(string.ascii_lowercase)
stringword=''
for x in word:
for letter in lower:
if x==ord(letter) - 96:
stringword=stringword+letter
break
for letter in upper:
if -x==ord(letter) - 64:
stringword=stringword+letter
break
return stringword
braid_word_to_letters([-1, -2, 3, 4, 3, -5, 4, 3, 2, 4, 1, 3, -5, 2, 4, 5, 6, 5, -7, -6, 5, -4, 3, -5, -4, -5, 6, -5, 7, 4, -3, -2, -3, 4, -5, -6])
'ABcdcEdcbdacEbdefeGFeDcEDEfEgdCBCdEF'
braid_word_to_letters([-1, 2, 3, -4, 5, -6, 7, 8, -9, -8, -7, 6, 5, 4, -3, -2, 1, 3, -4, 5, -6, 5, 7, 4, 8, -3, -5, 9, 2, 6, -5, 7, -4, -6, 3, -5, -6, -5, -4, -5, -6, -7, -6, -8, -5, -7, 4, -3, 5, -2, 6, 5, 4, -5])
'AbcDeFghIHGfedCBacDeFegdhCEibfEgDFcEFEDEFGFHEGdCeBfedE'